package jwt

import (
	"card/pkg/app"
	"card/pkg/tconfig"
	"errors"
	"github.com/gin-gonic/gin"
	jwtpkg "github.com/golang-jwt/jwt"
	"strings"
	"time"
)

var (
	ErrTokenExpired           error = errors.New("令牌已过期")
	ErrTokenExpiredMaxRefresh error = errors.New("令牌已过最大刷新时间")
	ErrTokenMalformed         error = errors.New("请求令牌格式有误")
	ErrTokenInvalid           error = errors.New("请求令牌无效")
	ErrHeaderEmpty            error = errors.New("需要认证才能访问！")
	ErrHeaderMalformed        error = errors.New("请求头中 Authorization 格式有误")
)

type JWT struct {
	SignKey    []byte
	MaxRefresh time.Duration
}

type JWTCustomClaims struct {
	UserID       string `json:"user_id"`
	ExpireAtTime int64  `json:"expire_time"`
	// StandardClaims 结构体实现了 Claims 接口继承了  Valid() 方法
	// JWT 规定了7个官方字段，提供使用:
	// - iss (issuer)：发布者
	// - sub (subject)：主题
	// - iat (Issued At)：生成签名的时间
	// - exp (expiration time)：签名过期时间
	// - aud (audience)：观众，相当于接受者
	// - nbf (Not Before)：生效时间
	// - jti (JWT ID)：编号
	jwtpkg.StandardClaims
}

func NewJwt() *JWT {
	return &JWT{
		SignKey:    []byte(tconfig.GetString("app.key")),
		MaxRefresh: tconfig.GetDuration("jwt.max_refresh_time") * time.Minute,
	}
}

func (jwt *JWT) IssueToken(userId string) (string, error) {
	expireAtTime := jwt.expireAtTime()
	claims := JWTCustomClaims{
		userId,
		expireAtTime,
		jwtpkg.StandardClaims{
			NotBefore: app.TimeNowInTimezone().Unix(), // 签名生效时间
			IssuedAt:  app.TimeNowInTimezone().Unix(), // 首次签名时间（后续刷新 Token 不会更新）
			ExpiresAt: expireAtTime,                   // 签名过期时间
			Issuer:    tconfig.GetString("app.name"),  // 签名颁发者
		},
	}
	return jwt.createToken(claims)
}

func (jwt *JWT) createToken(claims JWTCustomClaims) (string, error) {
	token := jwtpkg.NewWithClaims(jwtpkg.SigningMethodHS256, claims)
	return token.SignedString(jwt.SignKey)
}

func (jwt *JWT) ParseToken(c *gin.Context) (*JWTCustomClaims, error) {
	var (
		err         error
		tokenString string
		token       *jwtpkg.Token
	)
	tokenString, err = jwt.getTokenFromHeader(c)
	if err != nil {
		return nil, err
	}
	token, err = jwt.parseTokenToString(tokenString)
	// 2. 解析出错
	if err != nil {
		validationErr, ok := err.(*jwtpkg.ValidationError)
		if ok {
			if validationErr.Errors == jwtpkg.ValidationErrorMalformed {
				return nil, ErrTokenMalformed
			} else if validationErr.Errors == jwtpkg.ValidationErrorExpired {
				return nil, ErrTokenExpired
			}
		}
		return nil, ErrTokenInvalid
	}

	// 3. 将 token 中的 claims 信息解析出来和 JWTCustomClaims 数据结构进行校验
	if claims, ok := token.Claims.(*JWTCustomClaims); ok && token.Valid {
		return claims, nil
	}
	return nil, ErrTokenInvalid
}

func (jwt *JWT) parseTokenToString(tokenString string) (*jwtpkg.Token, error) {
	return jwtpkg.ParseWithClaims(tokenString, &JWTCustomClaims{}, func(token *jwtpkg.Token) (interface{}, error) {
		return jwt.SignKey, nil
	})
}

func (jwt *JWT) getTokenFromHeader(c *gin.Context) (string, error) {
	authHeader := c.Request.Header.Get("Authorization")
	if authHeader == "" {
		return "", ErrHeaderEmpty
	}
	attrs := strings.Split(authHeader, " ")
	if !(len(attrs) == 2 && attrs[0] == "Bearer") {
		return "", ErrHeaderMalformed
	}
	return attrs[1], nil
}

func (jwt *JWT) RefreshToken(c *gin.Context) (string, error) {
	var (
		err         error
		tokenString string
		token       *jwtpkg.Token
	)
	tokenString, err = jwt.getTokenFromHeader(c)
	if err != nil {
		return "", err
	}
	token, err = jwt.parseTokenToString(tokenString)
	// 3. 解析出错，未报错证明是合法的 Token（甚至未到过期时间）
	if err != nil {
		validationErr, ok := err.(*jwtpkg.ValidationError)
		// 满足 refresh 的条件：只是单一的报错 ValidationErrorExpired
		if !ok || validationErr.Errors != jwtpkg.ValidationErrorExpired {
			return "", err
		}
	}
	// 4. 解析 JWTCustomClaims 的数据
	claims := token.Claims.(*JWTCustomClaims)
	// 5. 检查是否过了『最大允许刷新的时间』
	x := app.TimeNowInTimezone().Add(-jwt.MaxRefresh).Unix()
	if claims.IssuedAt > x {
		// 修改过期时间
		claims.StandardClaims.ExpiresAt = jwt.expireAtTime()
		return jwt.createToken(*claims)
	}

	return "", ErrTokenExpiredMaxRefresh
}

func (jwt *JWT) expireAtTime() int64 {
	var expireTime int64
	timeNow := app.TimeNowInTimezone()
	expireTime = tconfig.GetInt64("jwt.expire_time")
	expire := time.Duration(expireTime) * time.Minute
	return timeNow.Add(expire).Unix()
}

func (jwt *JWT) LoginByTokenString(tokenString string) (string, error) {
	token, err := jwt.parseTokenToString(tokenString)
	// 2. 解析出错
	if err != nil {
		validationErr, ok := err.(*jwtpkg.ValidationError)
		if ok {
			if validationErr.Errors == jwtpkg.ValidationErrorMalformed {
				return "", ErrTokenMalformed
			} else if validationErr.Errors == jwtpkg.ValidationErrorExpired {
				return "", ErrTokenExpired
			}
		}
		return "", ErrTokenInvalid
	}

	// 3. 将 token 中的 claims 信息解析出来和 JWTCustomClaims 数据结构进行校验
	if claims, ok := token.Claims.(*JWTCustomClaims); ok && token.Valid {
		return claims.UserID, nil
	}
	return "", ErrTokenInvalid
}

func (jwt *JWT) RefreshByTokenString(tokenString string) (string, error) {
	token, err := jwt.parseTokenToString(tokenString)
	// 3. 解析出错，未报错证明是合法的 Token（甚至未到过期时间）
	if err != nil {
		validationErr, ok := err.(*jwtpkg.ValidationError)
		// 满足 refresh 的条件：只是单一的报错 ValidationErrorExpired
		if !ok || validationErr.Errors != jwtpkg.ValidationErrorExpired {
			return "", err
		}
	}
	// 4. 解析 JWTCustomClaims 的数据
	claims := token.Claims.(*JWTCustomClaims)
	// 5. 检查是否过了『最大允许刷新的时间』
	x := app.TimeNowInTimezone().Add(-jwt.MaxRefresh).Unix()
	if claims.IssuedAt > x {
		// 修改过期时间
		claims.StandardClaims.ExpiresAt = jwt.expireAtTime()
		return jwt.createToken(*claims)
	}

	return "", ErrTokenExpiredMaxRefresh
}
